package main

import "fmt"

func main() {
	/*
		Golang之方法调用
			原博客：//https://blog.csdn.net/weixin_52690231/article/details/124680043

		1.方法：方法在实现上与普通函数并无不同，只不过会通过运行时栈多传递一个隐含的参数，这个隐含的参数就是所谓的接收者
	*/

	fmt.Println("--------------------值接收者-----------------------")
	a := A{name: "喜羊羊"}

	//1.编译器的语法糖，提供面向对象的语法
	fmt.Println(a.Name())
	//2.更贴近真实实现的写法，和普通函数的调用几乎没什么不同
	fmt.Println(A.Name(a))

	fmt.Println(a.name) //a.name 的值并未发生改变
	/*
		1.编译器会帮我们将第一种写法转换为第二种写法
			a.Name() ——> A.Name(a) （方法调用的语法糖）

		2.执行过程中，函数调用栈的情况
		（1）局部变量 a 作为要传入的参数，被直接拷贝到参数空间
		（2）A.Name(a)执行时，修改的是 参数空间 的a.name,string底层指向的字符串内容发生了改变
		（3）函数返回前将返回值写入返回值空间，就是拷贝参数 a 的成员 name 到返回空间

		3.通过值接收者调用方法时，值接收者会作为第一个参数，
			而Go语言中传参都是值拷贝，所以执行 a.Name() 修改的并不是局部变量 a，而是拷贝过去的参数，要想修改 a，还得用指针接收者
	*/

	fmt.Println("--------------------指针接收者-----------------------")
	b := B{name: "灰太狼"}
	pb := &b
	fmt.Println(pb.Name()) //——> (*B).Name(pb)
	fmt.Println(b.name)    //b.name 的值发生了改变
	/*
		1.main 函数的栈帧有两个局部变量，pb 存储的是 b 的地址。
			pb.Name() 会由编译器转换为 (*B).Name(pb)函数调用，所以参数空间拷贝参数 pb 的值，也就是局部变量 b 的地址

		2.(*B).Name(pb)执行时，修改的是 pb 指向的结构体，也就是局部变量 b.name 的值。
			这个 string 类型的成员会指向新的底层字符串，而返回值空间被写入的也是 pb 指向的结构体成员name

		3.通过 指针类型接收者 调用方法时，指针会作为参数传入，传参时拷贝的就是地址，所以这里能够对原来变量 a 进行修改
	*/

	fmt.Println("----------------------更甜的语法糖-------------------------")
	a1 := A{name: "孙悟空"}
	pa1 := &a1

	fmt.Println(pa1.GetName()) //——> (*pa1).GetName() ——> A.GetName(*pa)
	fmt.Println(a1.SetName())  //——> (&a).SetName() ——> (*A).SetName(&a)
	/*
		1.这是语法糖，在编译期间 会发生如上转换，所以看似值接收者和指针接收者交叉访问了对方的方法，实际上依然遵循之前所说的执行过程
		（在定义的方法不涉及到接口类型时是这样的）
							      语法糖
			pa1.GeyName() ---(*pa1).GetName()--> A.GetName(*pa1)

								  语法糖
			a1.SetName() -----(&a).SetName()----> (*A).SetName(&a)

		2.这种 语法糖 是在 编译期间发挥作用的，像下面这种编译期间 不能拿到 地址的 字面量，就不能享受语法糖，转换成对应的指针接收者调用了
			fmt.Println(A{name: "小灰灰"}.SetName()) //A{name: "小灰灰"}是字面量
	*/

	fmt.Println("----------------- 方法表达式 & 方法值 -----------------")
	/*
		Go语言中，函数作为变量、参数和返回值时，都是以 Function Value 的形式存在的。也知道闭包只是有捕获列表（catch list）的 Function Value

	*/
	a2 := A{name: "杰瑞"}

	f1 := A.GetName // f1是方法表达式
	f1(a2)          //杰瑞

	f2 := a2.GetName // f2是方法变量
	f2()             //杰瑞
	/*
		1.方法表达式：
			像 f1 这样，把一个类型的方法赋给它这样变量就被称为“方法表达式”，对 f1 的处理相当于
				func GetName(a A) string{
					return a.name
				}
				func main(){
					a := A{name:"eggo"}

					f1 := GetName
					f1(a)
				}
			f1实际上就是一个普通的 Function Value，执行时需要传入一个 A 类型的变量作为第一个参数

		2.方法变量（作为局部变量）
			像 f2 这样，通过 a.GetName 进行赋值，这样的变量被称为“方法变量”。
			无需传入参数，因为编译器帮我们做了处理，方法变量也是一个 Function Value。
			编译阶段 f2() 会被转换成 A.GetName(a)
		注：这只是方法变量作为局部变量的情况

		3.方法变量（作为返回值）
			把方法变量作为返回值。这个返回值实际上是一个捕获了局部变量 a 的 Function Value，也就是说 f3 是一个闭包对象
			func GetFunc() func() string {
				a := A{name: "eggo in GetFunc"}
				return a.GetName
			}
			func main() {
				a := A{name: "eggo in main"}
				f2 := a.GetName
				fmt.Println(f2()) //这里输出：eggo in main

				f3 := GetFunc()
				fmt.Println(f3()) //这里输出：eggo in GetFunc
			}
		GetFunc 函数等价与下面的代码
			func GetFunc() (func()string) {
				a := A{name:"eggo in GetFunc"}

				return func()string{
					return A.GetName(a) //捕获变量a
				}
			}
	*/
}

type A struct {
	name string
}

func (a A) Name() string {
	a.name = "你好呀！" + a.name + "。。。值接收者"
	return a.name
}

type B struct {
	name string
}

func (b *B) Name() string {
	b.name = "你好呀！" + b.name + "。。。指针接收者"
	return b.name
}

func (a A) GetName() string {
	return a.name
}

func (pa *A) SetName() string {
	pa.name = "你好！" + pa.name
	return pa.name
}
